<?php

namespace app\common\library\xmwechat\payment;

use app\common\library\xmwechat\SingletonTrait;
use app\common\library\xmwechat\WechatService;
use app\common\model\Order;
use EasyWeChat\Pay\Application;
use Symfony\Component\HttpClient\Exception\ClientException;
use Symfony\Component\HttpClient\Exception\ServerException;
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
use think\facade\Log;
use think\Xml;

/**
 * 微信支付
 */
class PayService extends WechatService
{
    protected ?Application $app = null;

    use SingletonTrait;
    
    protected $config = [];

    public function __construct()
    {
        parent::__construct();
        if (empty($this->wechatPayConfig['mch_id']) || empty($this->wechatPayConfig['secret_key'])) {
            $this->result('请配置商户号或商户密钥Key');
        }
        $payConfig = [
            'mch_id'      => $this->wechatPayConfig['mch_id'],
            'secret_key'  => $this->wechatPayConfig['secret_key'],
            'certificate' => root_path() . $this->wechatPayConfig['certificate'],
            'private_key' => root_path() . $this->wechatPayConfig['private_key'],
            'notify_url'  => $this->wechatPayConfig['notify_url'],
            'http'        => [
                'throw'   => true, // 状态码非 200、300 时是否抛出异常，默认为开启
                'timeout' => 5.0,
            ],
        ];
        $this->config = $payConfig;
        $this->app = new Application($payConfig);
    }


    /**
     * 微信公众号、小程序发起支付
     * @return bool|array
     * @throws ClientExceptionInterface
     * @throws DecodingExceptionInterface
     * @throws RedirectionExceptionInterface
     * @throws ServerExceptionInterface
     * @throws TransportExceptionInterface
     * @throws \Exception
     * @author jsjxsz <QQ:1365962177>
     */
    /*public function pay($params): bool|array
    {

        try {
            // order_no:订单号，body:描述，pay_price:支付金额， openid：当前用户openid
            $order_no = $params['order_no'];
            $response = $this->app->getClient()->postJson("/v3/pay/transactions/jsapi", [
                "mchid"        => $this->wechatPayConfig['mch_id'],
                "out_trade_no" => $params['transaction_no'],// 交易号
                "appid"        => $this->miniProgramConfig['app_id'],
                "description"  => $params['body'],
                "notify_url"   => $this->wechatPayConfig['notify_url'],
                "amount"       => [
                    "total"    => intval($params['pay_price'] * 100),
                    "currency" => "CNY"
                ],
                "payer"        => [
                    "openid" => $params['openid']
                ]
            ]);
            $result   = $response->toArray();
            if (!empty($result['prepay_id'])) {
                $prepayId  = $result['prepay_id'];
                $utils     = $this->app->getUtils();
                //微信小程序appid; 公众号支付需用公众号appid
                $appId     = $params['pay_type'] == 'miniProgram' ? $this->miniProgramConfig['app_id'] : $this->offiAccountConfig['app_id'];
                // 默认RSA，v2要传MD5
                $signType  = 'RSA';
                $payResult = $utils->buildBridgeConfig($prepayId, $appId, $signType);
                if (isset($payResult['paySign']) && !empty($payResult['paySign'])) {

                    // 若有逻辑 这里写你自己的逻辑
                    // 此接口不代表支付成功，只是给客户端提供参数，调起支付
                    // 支付成功逻辑处理请在支付回调接口处理

                    // 此参数返回客户端，客户端拿到参数调起支付
                    return $payResult;
                }
            }
            return false;
        } catch (ServerException|ClientException $err) {
            $this->result($err->getResponse()->getContent(false));
        } catch (\Exception $exception) {
            $this->result($exception->getMessage());
        }
    }*/
    public function wxMchntAdd($params){
        $this->app->merchant->wxMchntAdd($params);
    }
    public function pay($params): bool|array
    {

        if($params['pay_type'] == 'H5'){
            $obj = $this->h5Pay($params);
        }else if($params['pay_type'] == 'miniProgram'){
            $obj = $this->miniProgram($params);
        }else{
            $obj = $this->offiAccount($params);
        }
        try {
            $response = $this->app->getClient()->postJson($obj['url'],$obj['body']);
            $result   = $response->toArray();
            if (!empty($result['prepay_id'])) {
                $prepayId  = $result['prepay_id'];
                $utils     = $this->app->getUtils();
                // 默认RSA，v2要传MD5
                $signType  = 'RSA';
                $payResult = $utils->buildBridgeConfig($prepayId, $obj['appId'], $signType);
                if (isset($payResult['paySign']) && !empty($payResult['paySign'])) {

                    // 若有逻辑 这里写你自己的逻辑
                    // 此接口不代表支付成功，只是给客户端提供参数，调起支付
                    // 支付成功逻辑处理请在支付回调接口处理

                    // 此参数返回客户端，客户端拿到参数调起支付
                    return $payResult;
                }
            }
            return false;
        } catch (ServerException|ClientException $err) {
            $this->result($err->getResponse()->getContent(false));
        } catch (\Exception $exception) {
            return $exception->getMessage();
            //$this->result($exception->getMessage());
        }
    }
    public function brokerPay($params): bool|array
    {

        if($params['pay_type'] == 'H5'){
            $obj = $this->h5Pay($params);
        }else if($params['pay_type'] == 'miniProgram'){
            $obj = $this->miniProgram($params);
        }else{
            $obj = $this->offiAccount($params);
        }
        try {
            $response = $this->app->getClient()->postJson($obj['url'],$obj['body']);
            $result   = $response->toArray();
            if (!empty($result['prepay_id'])) {
                $prepayId  = $result['prepay_id'];
                $utils     = $this->app->getUtils();
                // 默认RSA，v2要传MD5
                $signType  = 'RSA';
                $payResult = $utils->buildBridgeConfig($prepayId, $obj['appId'], $signType);
                if (isset($payResult['paySign']) && !empty($payResult['paySign'])) {

                    // 若有逻辑 这里写你自己的逻辑
                    // 此接口不代表支付成功，只是给客户端提供参数，调起支付
                    // 支付成功逻辑处理请在支付回调接口处理

                    // 此参数返回客户端，客户端拿到参数调起支付
                    return $payResult;
                }
            }
            return false;
        } catch (ServerException|ClientException $err) {
            $this->result($err->getResponse()->getContent(false));
        } catch (\Exception $exception) {
            return $exception->getMessage();
            //$this->result($exception->getMessage());
        }
    }
    public function h5Pay($params){
        $ip = request()->ip();
        $url = '/v3/pay/transactions/h5';
        $appid = config('xmwechat.h5.appid');
        $body = [
            [
                "appid"        => $appid,
                "mchid"        => $this->wechatPayConfig['mch_id'],
                "description"  => $params['body'],
                "out_trade_no" => $params['transaction_no'],// 交易号
                "notify_url"   => $this->wechatPayConfig['notify_url'],
                "amount"       => [
                    "total"    => intval($params['pay_price'] * 100),
                    "currency" => "CNY"
                ],
                "payer"        => [
                    "openid" => $params['openid']
                ],
                'scene_info'    =>[
                    'payer_client_ip'   =>$ip,      //用户终端id
                    'h5_info'           =>[
                        'type'              =>'Wap'
                    ],
                ]
            ]
        ];
        return ['url'=>$url,'body'=>$body,'appId'=>$appid];
    }

    public function miniProgram($params){
        $url = '/v3/pay/transactions/jsapi';
        $appid = $this->miniProgramConfig['app_id'];
        $body = [
            "mchid"        => $this->wechatPayConfig['mch_id'],
            "out_trade_no" => $params['transaction_no'],// 交易号
            "appid"        => $appid,
            "description"  => $params['body'],
            "notify_url"   => $params['notify_url'] ?? $this->wechatPayConfig['notify_url'],
            "amount"       => [
                "total"    => intval($params['pay_price'] * 100),
                "currency" => "CNY"
            ],
            "payer"        => [
                "openid" => $params['openid']
            ]
        ];
        return ['url'=>$url,'body'=>$body,'appId'=>$appid];
    }
    public function offiAccount($params){
        $url = '/v3/pay/transactions/jsapi';
        $appid = $this->offiAccountConfig['app_id'];
        $body = [
            "mchid"        => $this->wechatPayConfig['mch_id'],
            "out_trade_no" => $params['transaction_no'],// 交易号
            "appid"        => $appid,
            "description"  => $params['body'],
            "notify_url"   => $params['notify_url'] ?? $this->wechatPayConfig['notify_url'],
            "amount"       => [
                "total"    => intval($params['pay_price'] * 100),
                "currency" => "CNY"
            ],
            "payer"        => [
                "openid" => $params['openid']
            ]
        ];
        return ['url'=>$url,'body'=>$body,'appId'=>$appid];
    }

    /**
     * 退款（公众号、小程序）
     * @param $params
     * @return bool|string
     * @author jsjxsz <QQ:1365962177>
     */
    public function refunds($params): bool|string
    {
        try {
            $app      = $this->app;
            $response = $app->getClient()->postJson("/v3/refund/domestic/refunds", [
                "out_trade_no"  => $params['out_trade_no'],// 原支付交易对应的商户订单号
                "out_refund_no" => $params['out_refund_no'],// 商户退款单号，商户系统内部的退款单号，退款时生成，唯一
                "reason"        => $params['reason'], //退款原因
                "notify_url"    => $this->wechatPartnerPayConfig['refunds_notify_url'],
                "amount"        => [
                    // 退款金额，单位为分，只能为整数，不能超过原订单支付金额。
                    "refund"   => intval($params['refund_price'] * 100),
                    // 原支付交易的订单总金额，单位为分，只能为整数。
                    "total"    => intval($params['pay_price'] * 100),
                    "currency" => "CNY"
                ]
            ]);
            
            $result   = $response->toArray();
            if (isset($result['status'])) {
                $order = (new Order())->where('order_no', $result['out_trade_no'])->find();
                $order->refund_id = $result['refund_id'];
                $order->status = 70;
                $order->refund_time = time();
                $order->save();
                return $result['status'];
            }
        } catch (ServerException|ClientException|\Exception $exception) {
            Log::info('wechayPartnerRefunds:' . $exception->getMessage());
        }
        return false;
    }
    
    /**
     * 退款
     * @param float $totalFee 订单金额 单位元
     * @param float $refundFee 退款金额 单位元
     * @param string $refundNo 退款单号
     * @param string $wxOrderNo 微信订单号
     * @param string $orderNo 商户订单号
     * @return string
     */
    public function wxrefunds($params) {
        $config = array(
            'mch_id' => $this->wechatPayConfig['mch_id'],
            'appid' => $this->miniProgramConfig['app_id'],
            'key' => $this->wechatPayConfig['secret_key']
        );
        $unified = array(
            'appid' => $config['appid'],
            'mch_id' => $config['mch_id'],
            'nonce_str' => self::createNonceStr(),
            'total_fee' => intval($params['pay_price'] * 100), //订单金额  单位 转为分
            'refund_fee' => intval($params['refund_price'] * 100), //退款金额 单位 转为分
            'sign_type' => 'MD5', //签名类型 支持HMAC-SHA256和MD5，默认为MD5
            // 'transaction_id' => $wxOrderNo, //微信订单号
            'out_trade_no' => $params['out_trade_no'], //商户订单号 
            'out_refund_no' => $params['out_refund_no'], //商户退款单号
            'refund_desc' => '无理由', //退款原因（选填）
        );
        $unified['sign'] = self::getSign($unified, $config['key']);
        $responseXml = $this->curlPost('https://api.mch.weixin.qq.com/secapi/pay/refund', self::arrayToXml($unified));
        $unifiedOrder = simplexml_load_string($responseXml, 'SimpleXMLElement', LIBXML_NOCDATA);
        if ($unifiedOrder === false) {
            return false;
        }
        if ($unifiedOrder->return_code != 'SUCCESS') {
            return false;
        }
        // if ($unifiedOrder->result_code != 'SUCCESS') {
        //     return false;
        // }
  
        return true;
    }

    public static function curlGet($url = '', $options = array()) {
        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_TIMEOUT, 30);
        if (!empty($options)) {
            curl_setopt_array($ch, $options);
        }
        //https请求 不验证证书和host
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        $data = curl_exec($ch);
        curl_close($ch);
        return $data;
    }

    public function curlPost($url = '', $postData = '', $options = array()) {
        if (is_array($postData)) {
            $postData = http_build_query($postData);
        }
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
        curl_setopt($ch, CURLOPT_TIMEOUT, 30); //设置cURL允许执行的最长秒数
        if (!empty($options)) {
            curl_setopt_array($ch, $options);
        }
        //https请求 不验证证书和host
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        //第一种方法，cert 与 key 分别属于两个.pem文件
        //默认格式为PEM，可以注释
        curl_setopt($ch, CURLOPT_SSLCERTTYPE, 'PEM');
        curl_setopt($ch, CURLOPT_SSLCERT, $this->config['certificate']);//绝对路径
        //默认格式为PEM，可以注释
        curl_setopt($ch, CURLOPT_SSLKEYTYPE, 'PEM');
        curl_setopt($ch, CURLOPT_SSLKEY, $this->config['private_key']);//绝对路径
        //第二种方式，两个文件合成一个.pem文件
//    curl_setopt($ch,CURLOPT_SSLCERT,getcwd().'/all.pem');
        $data = curl_exec($ch);
        if ($data) {
            curl_close($ch);
            return $data;
        } else {
            $error = curl_errno($ch);
            echo "curl出错，错误码:$error" . "<br>";
            curl_close($ch);
            return false;
        }
    }

    public static function createNonceStr($length = 16) {
        $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
        $str = '';
        for ($i = 0; $i < $length; $i++) {
            $str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
        }
        return $str;
    }

    public function arrayToXml($arr) {
        $xml = "<xml>";
        foreach ($arr as $key => $val) {
            if (is_numeric($val)) {
                $xml .= "<" . $key . ">" . $val . "</" . $key . ">";
            } else {
                $xml .= "<" . $key . "><![CDATA[" . $val . "]]></" . $key . ">";
            }
        }
        $xml .= "</xml>";
        return $xml;
    }

    public static function xmlToArray($xml) {
        $array_data = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
        return $array_data;
    }

    public static function getSign($params, $key) {
        ksort($params, SORT_STRING);
        $unSignParaString = self::formatQueryParaMap($params, false);
        $signStr = strtoupper(md5($unSignParaString . "&key=" . $key));
        return $signStr;
    }

    protected static function formatQueryParaMap($paraMap, $urlEncode = false) {
        $buff = "";
        ksort($paraMap);
        foreach ($paraMap as $k => $v) {
            if (null != $v && "null" != $v) {
                if ($urlEncode) {
                    $v = urlencode($v);
                }
                $buff .= $k . "=" . $v . "&";
            }
        }
        $reqPar = '';
        if (strlen($buff) > 0) {
            $reqPar = substr($buff, 0, strlen($buff) - 1);
        }
        return $reqPar;
    }

}